package com.library.service;

import com.library.dto.BorrowRecordDTO;
import com.library.entity.Book;
import com.library.entity.BorrowRecord;
import com.library.entity.User;
import com.library.exception.BusinessException;
import com.library.exception.ForbiddenException;
import com.library.exception.NotFoundException;
import com.library.repository.BookRepository;
import com.library.repository.BorrowRecordRepository;
import com.library.repository.UserRepository;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.time.LocalDateTime;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

/**
 * 借阅服务类
 * 负责处理图书借阅、归还相关的业务逻辑
 * 包括借阅、归还申请、确认归还、查询借阅记录等功能
 */
@Slf4j
@Service
public class BorrowService {
    // 借阅记录数据访问层
    private final BorrowRecordRepository borrowRecordRepository;
    // 图书数据访问层
    private final BookRepository bookRepository;
    // 用户数据访问层
    private final UserRepository userRepository;
    // 预约服务（使用@Lazy延迟注入，避免循环依赖）
    private final ReservationService reservationService;
    // 通知服务（使用@Lazy延迟注入，避免循环依赖）
    private final NotificationService notificationService;

    // 构造函数注入，使用@Lazy避免循环依赖
    public BorrowService(
            BorrowRecordRepository borrowRecordRepository,
            BookRepository bookRepository,
            UserRepository userRepository,
            @Lazy ReservationService reservationService,
            @Lazy NotificationService notificationService) {
        this.borrowRecordRepository = borrowRecordRepository;
        this.bookRepository = bookRepository;
        this.userRepository = userRepository;
        this.reservationService = reservationService;
        this.notificationService = notificationService;
    }

    /**
     * 借阅图书
     * 用户借阅图书时，会创建借阅记录并减少图书库存
     * 管理员不能借阅图书
     * 
     * @param userId 用户ID
     * @param bookId 图书ID
     * @return 借阅记录DTO
     * @throws BusinessException 当ID无效、库存不足、已借阅或管理员尝试借阅时抛出
     * @throws NotFoundException 当用户或图书不存在时抛出
     */
    @Transactional  // 使用事务保证借阅和库存更新的一致性
    public BorrowRecordDTO borrowBook(Long userId, Long bookId) {
        // 验证用户ID
        if (userId == null || userId <= 0) {
            throw new BusinessException("用户ID无效");
        }
        // 验证图书ID
        if (bookId == null || bookId <= 0) {
            throw new BusinessException("图书ID无效");
        }
        
        // 1. 查找用户
        User user = userRepository.findById(userId)
                .orElseThrow(() -> {
                    log.warn("借阅失败: 用户不存在 - ID={}", userId);
                    return new NotFoundException("用户不存在");
                });

        // 2. 检查是否为管理员（管理员不能借阅）
        if ("admin".equals(user.getRole())) {
            log.warn("借阅失败: 管理员不能借阅图书 - 用户ID={}", userId);
            throw new BusinessException("管理员不能借阅图书");
        }

        // 3. 查找图书
        Book book = bookRepository.findById(bookId)
                .orElseThrow(() -> {
                    log.warn("借阅失败: 图书不存在 - ID={}", bookId);
                    return new NotFoundException("图书不存在");
                });

        // 4. 检查库存
        if (book.getStock() == null || book.getStock() <= 0) {
            log.warn("借阅失败: 图书库存不足 - 图书ID={}, 库存={}", bookId, book.getStock());
            throw new BusinessException("图书库存不足");
        }

        // 5. 检查用户是否已借阅该图书且未归还
        if (borrowRecordRepository.existsByUserIdAndBookIdAndStatus(userId, bookId, "borrowed")) {
            log.warn("借阅失败: 用户已借阅该图书 - 用户ID={}, 图书ID={}", userId, bookId);
            throw new BusinessException("您已借阅该图书，尚未归还");
        }

        // 6. 创建借阅记录
        BorrowRecord record = new BorrowRecord();
        record.setUser(user);
        record.setBook(book);
        record.setStatus("borrowed");  // 状态：已借阅
        record.setBorrowDate(LocalDateTime.now());
        // 设置到期日期（借阅期限30天）
        record.setDueDate(LocalDateTime.now().plusDays(30));
        record = borrowRecordRepository.save(record);

        // 7. 减少图书库存
        book.setStock(book.getStock() - 1);
        bookRepository.save(book);

        log.info("借阅成功: 用户ID={}, 图书ID={}, 记录ID={}", userId, bookId, record.getId());
        return convertToDTO(record);
    }

    /**
     * 申请归还图书
     * 用户提交归还申请，状态变为pending_return，等待管理员确认
     * 
     * @param userId 用户ID
     * @param recordId 借阅记录ID
     * @throws BusinessException 当ID无效或记录状态不正确时抛出
     * @throws NotFoundException 当借阅记录不存在时抛出
     * @throws ForbiddenException 当用户无权操作此记录时抛出
     */
    @Transactional
    public void returnBook(Long userId, Long recordId) {
        // 验证用户ID
        if (userId == null || userId <= 0) {
            throw new BusinessException("用户ID无效");
        }
        // 验证记录ID
        if (recordId == null || recordId <= 0) {
            throw new BusinessException("借阅记录ID无效");
        }
        
        // 1. 查找借阅记录
        BorrowRecord record = borrowRecordRepository.findById(recordId)
                .orElseThrow(() -> {
                    log.warn("归还失败: 借阅记录不存在 - ID={}", recordId);
                    return new NotFoundException("借阅记录不存在");
                });

        // 2. 检查权限：只能归还自己的借阅记录
        if (!record.getUser().getId().equals(userId)) {
            log.warn("归还失败: 无权操作此记录 - 用户ID={}, 记录ID={}", userId, recordId);
            throw new ForbiddenException("无权操作此记录");
        }

        // 3. 检查记录状态：只能归还状态为"borrowed"的记录
        if (!"borrowed".equals(record.getStatus())) {
            log.warn("归还失败: 记录状态不正确 - 记录ID={}, 状态={}", recordId, record.getStatus());
            throw new BusinessException("该记录已归还或待确认");
        }

        // 4. 更新状态为待确认归还
        record.setStatus("pending_return");
        borrowRecordRepository.save(record);
        log.info("归还申请已提交: 用户ID={}, 记录ID={}", userId, recordId);
    }

    /**
     * 管理员确认归还
     * 管理员确认归还后，更新记录状态并恢复图书库存
     * 
     * @param recordId 借阅记录ID
     * @throws BusinessException 当ID无效或记录状态不正确时抛出
     * @throws NotFoundException 当借阅记录不存在时抛出
     */
    @Transactional
    public void confirmReturn(Long recordId) {
        // 验证记录ID
        if (recordId == null || recordId <= 0) {
            throw new BusinessException("借阅记录ID无效");
        }
        
        // 1. 查找借阅记录
        BorrowRecord record = borrowRecordRepository.findById(recordId)
                .orElseThrow(() -> {
                    log.warn("确认归还失败: 借阅记录不存在 - ID={}", recordId);
                    return new NotFoundException("借阅记录不存在");
                });

        // 2. 检查是否已确认归还
        if ("returned".equals(record.getStatus())) {
            log.warn("确认归还失败: 记录已确认归还 - ID={}", recordId);
            throw new BusinessException("该记录已确认归还");
        }

        // 3. 检查记录状态：只能确认状态为"pending_return"的记录
        if (!"pending_return".equals(record.getStatus())) {
            log.warn("确认归还失败: 记录状态不正确 - ID={}, 状态={}", recordId, record.getStatus());
            throw new BusinessException("只能确认待归还状态的记录");
        }

        // 4. 更新记录状态和归还时间
        record.setStatus("returned");
        record.setReturnDate(LocalDateTime.now());
        borrowRecordRepository.save(record);

        // 5. 恢复图书库存
        Book book = record.getBook();
        book.setStock(book.getStock() + 1);
        bookRepository.save(book);
        
        // 6. 通知预约用户（如果有预约）
        try {
            reservationService.notifyAvailableReservations(book.getId());
        } catch (Exception e) {
            log.warn("通知预约用户失败: {}", e.getMessage());
        }
        
        log.info("确认归还成功: 记录ID={}, 图书ID={}", recordId, book.getId());
    }

    /**
     * 获取当前借阅列表
     * 普通用户只能查看自己的，管理员可以查看所有的
     * 
     * @param userId 用户ID
     * @param isAdmin 是否为管理员
     * @return 当前借阅记录列表（状态为borrowed或pending_return）
     */
    public List<BorrowRecordDTO> getCurrentBorrows(Long userId, boolean isAdmin) {
        // 验证用户ID
        if (userId == null || userId <= 0) {
            throw new BusinessException("用户ID无效");
        }
        
        List<BorrowRecord> records;
        if (isAdmin) {
            // 管理员查看所有当前借阅
            records = borrowRecordRepository.findAllCurrentBorrows();
            log.debug("管理员查询所有当前借阅: {} 条", records.size());
        } else {
            // 普通用户只查看自己的
            records = borrowRecordRepository.findCurrentBorrowsByUserId(userId);
            log.debug("用户查询当前借阅: 用户ID={}, {} 条", userId, records.size());
        }
        // 转换为DTO返回
        return records.stream().map(this::convertToDTO).collect(Collectors.toList());
    }

    /**
     * 获取借阅记录列表
     * 支持按状态筛选，普通用户只能查看自己的，管理员可以查看所有的
     * 
     * @param userId 用户ID
     * @param isAdmin 是否为管理员
     * @param status 借阅状态（borrowed/pending_return/returned），null表示查询所有状态
     * @return 借阅记录列表
     */
    public List<BorrowRecordDTO> getBorrowRecords(Long userId, boolean isAdmin, String status) {
        // 验证用户ID
        if (userId == null || userId <= 0) {
            throw new BusinessException("用户ID无效");
        }
        
        List<BorrowRecord> records;
        if (isAdmin) {
            // 管理员查看所有记录
            records = borrowRecordRepository.findAllRecords(status);
            log.debug("管理员查询所有借阅记录: 状态={}, {} 条", status, records.size());
        } else {
            // 普通用户只查看自己的
            records = borrowRecordRepository.findRecordsByUserId(userId, status);
            log.debug("用户查询借阅记录: 用户ID={}, 状态={}, {} 条", userId, status, records.size());
        }
        // 转换为DTO返回
        return records.stream().map(this::convertToDTO).collect(Collectors.toList());
    }

    /**
     * 获取借阅统计信息
     * 
     * @param userId 用户ID（如果为null，则统计所有用户）
     * @param isAdmin 是否为管理员
     * @return 借阅统计信息
     */
    public Map<String, Object> getBorrowStatistics(Long userId, boolean isAdmin) {
        List<BorrowRecord> records;
        if (isAdmin && userId == null) {
            // 管理员查看所有记录
            records = borrowRecordRepository.findAllRecords(null);
        } else if (isAdmin) {
            // 管理员查看指定用户的记录
            records = borrowRecordRepository.findRecordsByUserId(userId, null);
        } else {
            // 普通用户查看自己的记录
            records = borrowRecordRepository.findRecordsByUserId(userId, null);
        }

        // 统计信息
        long totalBorrows = records.size();
        long currentBorrows = records.stream().filter(r -> "borrowed".equals(r.getStatus())).count();
        long pendingReturns = records.stream().filter(r -> "pending_return".equals(r.getStatus())).count();
        long returned = records.stream().filter(r -> "returned".equals(r.getStatus())).count();

        // 最受欢迎图书（按借阅次数）
        Map<Long, Long> bookBorrowCount = records.stream()
                .collect(Collectors.groupingBy(r -> r.getBook().getId(), Collectors.counting()));

        Map<String, Object> statistics = new HashMap<>();
        statistics.put("totalBorrows", totalBorrows);
        statistics.put("currentBorrows", currentBorrows);
        statistics.put("pendingReturns", pendingReturns);
        statistics.put("returned", returned);
        statistics.put("bookBorrowCount", bookBorrowCount);

        log.info("获取借阅统计: 总借阅数={}, 当前借阅数={}", totalBorrows, currentBorrows);
        return statistics;
    }

    /**
     * 将BorrowRecord实体转换为BorrowRecordDTO
     * 
     * @param record 借阅记录实体
     * @return 借阅记录DTO
     */
    private BorrowRecordDTO convertToDTO(BorrowRecord record) {
        BorrowRecordDTO dto = new BorrowRecordDTO();
        dto.setId(record.getId());
        dto.setUserId(record.getUser().getId());
        dto.setUsername(record.getUser().getUsername());
        dto.setBookId(record.getBook().getId());
        dto.setTitle(record.getBook().getTitle());
        dto.setAuthor(record.getBook().getAuthor());
        dto.setIsbn(record.getBook().getIsbn());
        dto.setCategory(record.getBook().getCategory());
        dto.setBorrowDate(record.getBorrowDate());
        dto.setReturnDate(record.getReturnDate());
        dto.setStatus(record.getStatus());
        if (record.getDueDate() != null) {
            dto.setDueDate(record.getDueDate());
        }
        return dto;
    }
}
